﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Perceiveit.Xml
{
    public abstract class XProperty
    {

        internal const bool DefaultIsVolatile = false;

        public XName Name { get; internal set; }

        public PropertyType Type { get; internal set; }

        public bool Volatile { get; internal set; }


        //
        // Attribute<T> factory methods
        //

        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element.
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name)
        {
            return Attribute<T>((XName)name, DefaultIsVolatile, XConverter<T>.CreateConverter());
        }

        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name, bool isVolatile)
        {
            return Attribute<T>((XName)name, isVolatile, XConverter<T>.CreateConverter());
        }


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name, XConverter<T> converter)
        {
            return Attribute<T>((XName)name, DefaultIsVolatile, converter);
        }

        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name, bool isVolatile, XConverter<T> converter)
        {
            return Attribute<T>((XName)name, isVolatile, converter);
        }



        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        #region public static XProperty<T> Attribute<T>(string name, string namespaceName)

        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        
        public static XProperty<T> Attribute<T>(string name, string namespaceName)
        {
            return Attribute<T>(name, namespaceName, DefaultIsVolatile, XConverter<T>.CreateConverter());
        }

        #endregion


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name, string namespaceName, bool isVolatile)
        {
            return Attribute<T>(name, namespaceName, isVolatile, XConverter<T>.CreateConverter());
        }


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name, string namespaceName, XConverter<T> converter)
        {
            return Attribute<T>(name, namespaceName, DefaultIsVolatile, converter);
        }


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// NOTE: Attributes in the default namespace should not have a namespace value!
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - required</param>
        /// <param name="namespaceName">The namespace of the element - required</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(string name, string namespaceName, bool isVolatile, XConverter<T> converter)
        {
            return Attribute<T>(XName.Get(name, namespaceName), isVolatile, converter);
        }


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the fully qualified name on the parent XEntity's element
        /// </summary>
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="name">The fully qualified name of the attribute</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(XName name)
        {
            return Attribute<T>(name, false, XConverter<T>.CreateConverter());
        }


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// </summary>
        #region public static XProperty<T> Attribute<T>(XName name, bool isVolatile)

        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="name">The fully qualified name of the attribute</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        
        public static XProperty<T> Attribute<T>(XName name, bool isVolatile)
        {
            return Attribute<T>(name, isVolatile, XConverter<T>.CreateConverter());
        }

        #endregion


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// </summary>
        #region public static XProperty<T> Attribute<T>(XName name, XConverter<T> converter)

        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="name">The fully qualified name of the attribute</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        
        public static XProperty<T> Attribute<T>(XName name, XConverter<T> converter)
        {
            return Attribute<T>(name, DefaultIsVolatile, converter);
        }

        #endregion


        /// <summary>
        /// Creates a new property of type T where the value is extracted from an attribute
        /// with the specified localName and namespace on the parent XEntity's element
        /// </summary>
        #region public static XProperty<T> Attribute<T>(XName name, bool isVolatile, XConverter<T> converter)
        
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="name">The fully qualified name of the attribute</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new poperty to extract the value from an XEntity's underlying xml data</returns>
        public static XProperty<T> Attribute<T>(XName name, bool isVolatile, XConverter<T> converter)
        {
            if (null == name)
                throw new ArgumentNullException("name");
            if (null == converter)
                throw new ArgumentNullException("converter");

            XProperty<T> prop = new XPropertyAttribute<T>()
                {
                    Converter = converter,
                    Name = name,
                    Type = PropertyType.Attribute,
                    Volatile = isVolatile
                };

            return prop;

        }

        #endregion


        //
        // Text<T>
        //
        
        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element with the specified localName and namespace
        /// </summary>
        #region public static XProperty<T> Text<T>(string localName, string namespaceName)
        
        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="namespaceName">The namespace of the element - can be null if it is the content of the parent element</param>
        /// <returns>A new poperty to extract the value from an XEntity's unerlying xml data</returns>
        
        public static XProperty<T> Text<T>(string localName, string namespaceName)
        {

            //Get the default converter for the type T and call the main implementation method with
            //this and the Default is volatile value

            XConverter<T> conv = XConverter<T>.CreateConverter();
            return Text<T>(localName, namespaceName, DefaultIsVolatile, conv);
        }

        #endregion

        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element with the specified localName and namespace. If the 
        /// property is volatile its value is always re-read from the underlying xml
        /// </summary>
        #region public static XProperty<T> Text<T>(string localName, string namespaceName, bool isVolatile)

        /// <typeparam name="T">The type of the properties value</typeparam>
        /// <param name="localName">The local name of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="namespaceName">The namespace of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="isVolatile">A flag to specify if this property should alwasy read from the underlying xml data, 
        /// or cache the value for subsequent reads.</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>
        
        public static XProperty<T> Text<T>(string localName, string namespaceName, bool isVolatile)
        {
            //Get the default converter for the type T and call the main implementation method with
            //this and the isVolatile value

            XConverter<T> conv = XConverter<T>.CreateConverter();
            return Text<T>(localName, namespaceName, isVolatile, conv);
        }

        #endregion

        /// <summary>
        /// Creates a new property of type T,where the value is extracted from the text
        /// content of the element with the specified localName and namespace and converted to and 
        /// from a string using the specified converter
        /// </summary>
        #region public static XProperty<T> Text<T>(string localName, string namespaceName, XConverter<T> converter)
         
        /// <typeparam name="T">The type of the proprty's value</typeparam>
        /// <param name="localName">The local name of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="namespaceName">The namespace of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>
        
        public static XProperty<T> Text<T>(string localName, string namespaceName, XConverter<T> converter)
        {
            //Call the main implementation with the default isVolatile value

            return Text<T>(localName, namespaceName, DefaultIsVolatile, converter);
        }

        #endregion

        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element using the converter with the specified localName and namespace. If the 
        /// property is volatile its value is always re-read from the underlying xml
        /// </summary>
        #region public static XProperty<T> Text<T>(string localName, string namespaceName, bool isVolatile, XConverter<T> converter)

        /// <typeparam name="T">The type of the proprty's value</typeparam>
        /// <param name="localName">The local name of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="namespaceName">The namespace of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="isVolatile">True if the value may be changed by another source and must therefore be read from the xml everytime</param>
        /// <param name="converter">The converter to use to convert to and from a string</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>
        
        public static XProperty<T> Text<T>(string localName, string namespaceName, bool isVolatile, XConverter<T> converter)
        {
            if (string.IsNullOrEmpty(localName))
                return Text<T>((XName)null, isVolatile, converter);
            else
                return Text<T>(XName.Get(localName, namespaceName), isVolatile, converter);
        }

        #endregion



        #region public static XProperty<T> Text<T>() + 3 overloads

        /// <summary>
        /// Creates a new XPropertry of type T where the value is extracted from the text of the PARENT element
        /// rather than the text content of an inner element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static XProperty<T> Text<T>()
        {
            XConverter<T> converter = XConverter<T>.CreateConverter();
            return Text<T>(null, DefaultIsVolatile, converter);
        }

        /// <summary>
        /// Creates a new XPropertry of type T where the value is extracted from the text of the PARENT element
        /// rather than the text content of an inner element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="isVolatile"></param>
        /// <returns></returns>
        public static XProperty<T> Text<T>(bool isVolatile)
        {
            XConverter<T> converter = XConverter<T>.CreateConverter();
            return Text<T>(null, isVolatile, converter);
        }

        /// <summary>
        /// Creates a new XPropertry of type T where the value is extracted from the text of the PARENT element
        /// rather than the text content of an inner element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="converter"></param>
        /// <returns></returns>
        public static XProperty<T> Text<T>(XConverter<T> converter)
        {
            return Text<T>(null, DefaultIsVolatile, converter);
        }

        /// <summary>
        /// Creates a new XPropertry of type T where the value is extracted from the text of the PARENT element
        /// rather than the text content of an inner element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="isVolatile"></param>
        /// <param name="converter"></param>
        /// <returns></returns>
        public static XProperty<T> Text<T>(bool isVolatile, XConverter<T> converter)
        {
            return Text<T>(null, isVolatile, converter);
        }

        #endregion


        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element with the specified XName.
        /// </summary>
        #region public static XProperty<T> Text<T>(XName name)

        /// <typeparam name="T">The type of the proprty's value</typeparam>
        /// <param name="name">The XName of the element containing the text - can be null if it is the content of the parent element</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>
        
        public static XProperty<T> Text<T>(XName name)
        {
            XConverter<T> converter = XConverter<T>.CreateConverter();
            return Text<T>(name, DefaultIsVolatile, converter);
        }

        #endregion

        
        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element with the specified XName. If
        /// isVolatile then the value will always be read from the underlying Xml rather
        /// than a cached value
        /// </summary>
        #region public static XProperty<T> Text<T>(XName name, bool isVolatile)

        /// <typeparam name="T">The type of the proprty's value</typeparam>
        /// <param name="name">The XName of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="isVolatile">True if the value of the property should always be read from the underlying XML</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>

        public static XProperty<T> Text<T>(XName name, bool isVolatile)
        {
            XConverter<T> converter = XConverter<T>.CreateConverter();
            return Text<T>(name, isVolatile, converter);
        }

        #endregion

        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element with the specified XName using the converter provided.
        /// </summary>
        #region public static XProperty<T> Text<T>(XName name, XConverter<T> converter)

        /// <typeparam name="T">The type of the proprty's value</typeparam>
        /// <param name="name">The XName of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="converter">The converter instance to use to convert the string value to and from type T</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>

        public static XProperty<T> Text<T>(XName name, XConverter<T> converter)
        {
            return Text<T>(name, DefaultIsVolatile, converter);
        }

        #endregion

        /// <summary>
        /// Creates a new property of type T where the value is extracted from the text
        /// content of the element with the specified XName using the converter provided. If
        /// isVolatile then the value will always be read from the underlying Xml rather
        /// than a cached value
        /// </summary>
        #region public static XProperty<T> Text<T>(XName name, bool isVolatile, XConverter<T> converter)

        /// <typeparam name="T">The type of the proprty's value</typeparam>
        /// <param name="name">The XName of the element containing the text - can be null if it is the content of the parent element</param>
        /// <param name="converter">The converter instance to use to convert the string value to and from type T</param>
        /// <param name="isVolatile">True if the value of the property should always be read from the underlying XML</param>
        /// <returns>A new property to extract the value from an XEntity's unerlying xml data</returns>

        public static XProperty<T> Text<T>(XName name, bool isVolatile, XConverter<T> converter)
        {
            if (null == converter)
                throw new ArgumentNullException("converter");

            XProperty<T> prop = new XPropertyText<T>()
            {
                Converter = converter,
                Name = name,
                Type = PropertyType.Text,
                Volatile = isVolatile
            };

            return prop;

        }

        #endregion


        #region public static XanProperty<T> Complex<T>(string elementName, string namespaceName) + 3 overloads

        public static XProperty<T> Complex<T>(string elementName, string namespaceName) where T : XEntity, new()
        {
            XName name = XName.Get(elementName, namespaceName);
            return Complex<T>(name);
        }

        public static XProperty<T> Complex<T>(string elementName, string namespaceName, XFactory<T> factory) where T : XEntity
        {
            XName name = XName.Get(elementName, namespaceName);
            return Complex<T>(name, factory);
        }

        public static XProperty<T> Complex<T>(XName name) where T : XEntity, new()
        {
            XSimpleFactory<T> fact = new XSimpleFactory<T>(name);
            return Complex<T>(name, fact);
        }

        public static XProperty<T> Complex<T>(XName name, XFactory<T> factory) where T : XEntity
        {
            XProperty<T> prop = new XPropertyComplex<T>(factory);
            prop.Name = name;
            prop.Type = PropertyType.Complex;
            //TODO: Support the use of factories in complex properties
            return prop;
        }

        #endregion


        public static XPropertyList<T> List<T>(string listname, string namespaceName, string innerElementName) where T : XEntity, new()
        {
            XSimpleFactory<T> childfactory = new XSimpleFactory<T>(innerElementName, namespaceName);
            return List<T>(listname, namespaceName, childfactory);
        }

        public static XPropertyList<T> List<T>(string listName, string namespaceName, XFactory<T> innerElementFactory) where T : XEntity
        {
            XName xname = XName.Get(listName, namespaceName);
            return List<T>(xname, innerElementFactory);
            
            
        }

        public static XPropertyList<T> List<T>(XName list, XName innerElement) where T : XEntity, new()
        {
            XSimpleFactory<T> childfactory = new XSimpleFactory<T>(innerElement);
            return List<T>(list, childfactory);
        }


        public static XPropertyList<T> List<T>(XName list, XFactory<T> innerElementFactory) where T : XEntity
        {
            XFactory<XList<T>> parentfactory = new XListFactory<T>(innerElementFactory, list);
            XPropertyList<T> prop = new XPropertyList<T>(parentfactory);
            prop.Name = list;
            prop.Type = PropertyType.Complex;

            return prop;

        }


    }
}
